/**
 * 富文本解析器
 */
import { Button, CCObject, CacheMode, Color, Component, Font, HorizontalTextAlignment, Label, LabelOutline, LabelShadow, Node, Prefab, Sprite, SpriteFrame, UITransform, Vec2, VerticalTextAlignment, _decorator, instantiate } from 'cc';
import { StringUtil } from '../../Util/StringUtil';
import { XMLParser } from '../../Util/XMLParser';
const { ccclass, property, requireComponent, executeInEditMode, menu } = _decorator;

const RichTextLabelName = '#RichTextLabel';
const RichTextImageName = '#RichTextImage';
const RichTextNodeName = '#RichTextNode';

const Tooltip = `文本内容
* 内置的标签:
* label:    文本标签
* image:    图片标签
* node:     节点标签
* line:     整行标签(独占一整行)
* br:       换行标签
* 
* br标签(不支持嵌套)
* 
* line标签(不支持嵌套)
* 支持的属性:
* line-height: 行高            line-height=10
* 
* label标签(支持嵌套，嵌套会继承属性)
* 支持的属性:
* click: 点击事件               click=消息
* offset-y: 偏移Y              offset-y=10
* line-height: 行高            line-height=10
* color: 颜色                  color=#000000
* font: font索引               font=下标
* family: 字体                 family=Arial
* size: 字体大小                size=16
* bold: 字体粗体                bold
* italic: 字体斜体              italic
* underline: 字体下划线         underline
* outline-color: 描边颜色       outline-color=#000000
* outline-width: 描边宽度       outline-width=1
* shadow-color: 阴影颜色        shadow-color=#000000
* shadow-offset: 阴影偏移x,y    shadow-offset=1,2
* shadow-blur: 阴影模糊         shadow-blur=1
* vertical-align: 垂直对齐      top center bottom
*
* image标签(不支持嵌套)
* 支持的属性:
* click: 点击事件
* offset-y: 偏移Y
* line-height: 行高
* frame: frame索引
* width: 宽度(默认lineHeight)
* height: 高度(默认lineHeight)
* vertical-align: 垂直对齐
*
* node标签(不支持嵌套)
* 支持的属性:
* click: 点击事件
* offset-y: 偏移Y
* line-height: 行高
* prefab: prefab索引
* width: 宽度(默认lineHeight)
* height: 高度(默认lineHeight)
* vertical-align: 垂直对齐
`;

function createNode(name: string) {
  const node = new Node(name);
  node.hideFlags |= CCObject.Flags.DontSave | CCObject.Flags.HideInHierarchy;
  return node;
}

class RichLabelPool {
  private static _inst: RichLabelPool;
  static get inst() {
    if (!this._inst) this._inst = new RichLabelPool();
    return this._inst;
  }

  private pool: Label[] = [];

  public getLabel(): Label {
    if (this.pool.length > 0) {
      return this.pool.pop();
    }
    const node = createNode(RichTextLabelName);
    return node.addComponent(Label);
  }

  public putLabel(label: Label) {
    label.font = null;
    label.string = '';
    label.node.removeFromParent();
    label.node.targetOff(this);
    this.pool.push(label);
  }
}

enum WholenessType {
  None,
  Chinese,
  Number,
  Letter,
  Space,
}

function isChinese(char: string) {
  const charCode = char.charCodeAt(0);
  return charCode >= 19968 && charCode <= 40869;
}

function isNumber(char: string) {
  const charCode = char.charCodeAt(0);
  return charCode >= 48 && charCode <= 57;
}

function isLetter(char: string) {
  const charCode = char.charCodeAt(0);
  return (charCode >= 65 && charCode <= 90) || (charCode >= 97 && charCode <= 122);
}

function getWholenessType(char: string, lastType: WholenessType): WholenessType {
  if (char === ' ') return WholenessType.Space;
  if (char === '.') return WholenessType.Letter;
  if (isNumber(char) || isLetter(char)) return WholenessType.Letter;
  if (isChinese(char)) return WholenessType.Chinese;

  return lastType;
}

function getFirstWholeness(input: string): { type: WholenessType; text: string } {
  const result: { type: WholenessType; text: string } = { type: WholenessType.None, text: '' };

  for (let index = 0; index < input.length; index++) {
    const char = input.charAt(index);

    const type = getWholenessType(char, result.type);
    if (result.type !== WholenessType.None && result.type !== type) {
      break;
    }

    result.type = type;
    result.text += char;
  }
  return result;
}

function testLabelLength(length: number, callback: (num: number) => number): number {
  let min = 0;
  let max = length;
  while (min < max) {
    if (min === max - 1) {
      if (callback(max) >= 0) {
        return max;
      }
      // 为了触发一次label更新
      callback(min);
      return min;
    }
    const mid = Math.floor((max - min) / 2) + min;
    const result = callback(mid);
    if (result >= -1 && result <= 1) {
      return mid;
    } else if (result < 0) {
      max = mid;
    } else {
      min = mid;
    }
  }
  return min;
}

function defaultLabelSplit(label: Label, labelUit: UITransform, diff: number, text: string) {
  const length = testLabelLength(text.length, (length) => {
    label.string = text.slice(0, length);
    label.updateRenderData(true);
    return diff - labelUit.width;
  });
  return length;
}

interface IAttrs {
  click?: string;
  color?: string;
  font?: string;
  family?: string;
  size?: string;
  bold?: string;
  italic?: string;
  underline?: string;
  'offset-y'?: string;
  'outline-color'?: string;
  'outline-width'?: string;
  'shadow-color'?: string;
  'shadow-offset'?: string; // 阴影偏移 格式为 x,y
  'shadow-blur'?: string;
  'line-height'?: string;
  // 'horizontal-align'?: 'left' | 'center' | 'right';
  'vertical-align'?: 'top' | 'center' | 'bottom';
  width?: string;
  height?: string;
  [key: string]: string;
}

interface Line {
  width: number;
  height: number;
  lineHeight: number;
  nodes: {
    node: Node;
    attrs: IAttrs;
  }[];
}

@ccclass('RichTextRedefine')
@menu('myComponent/RichTextRedefine')
@executeInEditMode(true)
@requireComponent(UITransform)
export class RichTextRedefine extends Component {
  static EventType = Button.EventType;

  @property
  private _color = new Color();
  @property({
    type: Color,
    tooltip: '文本颜色',
  })
  public get color() {
    return this._color;
  }
  public set color(value) {
    this._color = value;
    this.updateRender();
  }

  @property
  private _string: string = '';
  @property({
    multiline: true,
    tooltip: Tooltip,
  })
  public get string(): string {
    return this._string;
  }
  public set string(value: string) {
    this._string = value;
    this.updateRender();
  }

  @property
  private _horizontalAlign = HorizontalTextAlignment.CENTER;
  @property({ type: HorizontalTextAlignment, tooltip: '文本水平对齐方式' })
  public get horizontalAlign() {
    return this._horizontalAlign;
  }
  public set horizontalAlign(value) {
    this._horizontalAlign = value;
    this.updateRender();
  }

  @property
  private _verticalAlign = VerticalTextAlignment.CENTER;
  @property({ type: VerticalTextAlignment, tooltip: '文本垂直对齐方式' })
  public get verticalAlign() {
    return this._verticalAlign;
  }
  public set verticalAlign(value) {
    this._verticalAlign = value;
    this.updateRender();
  }

  @property
  private _fontSize = 40;
  @property({ tooltip: '文本字号' })
  public get fontSize() {
    return this._fontSize;
  }
  public set fontSize(value) {
    this._fontSize = value;
    this.updateRender();
  }

  @property
  private _lineHeight = 40;
  @property({ tooltip: '文本行高' })
  public get lineHeight() {
    return this._lineHeight;
  }
  public set lineHeight(value) {
    this._lineHeight = value;
    this.updateRender();
  }

  @property
  private _maxWidth = 0;
  @property({ tooltip: '文本最大宽度' })
  public get maxWidth() {
    return this._maxWidth;
  }
  public set maxWidth(value) {
    this._maxWidth = value;
    this.updateRender();
  }

  @property
  private _wholeness = true;
  @property({
    tooltip: '是否保证数字和单词的完整性',
  })
  public get wholeness() {
    return this._wholeness;
  }
  public set wholeness(value) {
    this._wholeness = value;
    this.updateRender();
  }

  @property
  private _useSystemFont = true;
  @property
  public get useSystemFont() {
    return this._useSystemFont;
  }
  public set useSystemFont(value) {
    this._useSystemFont = value;
    this.updateRender();
  }

  @property
  private _fontFamily = 'Arial';
  @property({
    tooltip: '文本字体',
    visible() {
      return this._useSystemFont;
    },
  })
  public get fontFamily() {
    return this._fontFamily;
  }
  public set fontFamily(value) {
    this._fontFamily = value;
    if (this._useSystemFont) this.updateRender();
  }

  @property
  private _fonts: Font[] = [];
  @property({
    type: [Font],
    tooltip: '文本字体',
    visible() {
      return !this._useSystemFont;
    },
  })
  public get fonts(): Font[] {
    return this._fonts;
  }
  public set fonts(value: Font[]) {
    this._fonts = value;
    if (!this._useSystemFont) this.updateRender();
  }

  @property
  private _frames: SpriteFrame[] = [];
  @property({
    type: [SpriteFrame],
    tooltip: '图片资源',
  })
  public get frames(): SpriteFrame[] {
    return this._frames;
  }
  public set frames(value: SpriteFrame[]) {
    this._frames = value;
    this.updateRender();
  }

  @property
  private _prefabs: Prefab[] = [];
  @property({
    type: [Prefab],
    tooltip: '预制资源',
  })
  public get prefabs(): Prefab[] {
    return this._prefabs;
  }
  public set prefabs(value: Prefab[]) {
    this._prefabs = value;
    this.updateRender();
  }

  @property
  private _cacheMode: CacheMode = CacheMode.NONE;
  @property({
    type: CacheMode,
    tooltip: '缓存模式:\n1.NONE:不缓存\n2.BITMAP:以文本段为单位作为静态图像加入动态图集进行批次合并，但不能频繁动态修改文本内容\n3.CHAR:以但字符为单位作为静态图像加入动态图集进行批次合并，可以频繁动态修改文本内容',
  })
  public get cacheMode(): CacheMode {
    return this._cacheMode;
  }
  public set cacheMode(value: CacheMode) {
    this._cacheMode = value;
    this.updateRender();
  }

  private lines: Line[] = null;

  protected onLoad(): void {
    this.updateRender();
    this.node.on(Node.EventType.LAYER_CHANGED, this.updateLayer, this);
    this.node.on(Node.EventType.ANCHOR_CHANGED, this.updatePos, this);
  }

  protected onDestroy(): void {
    this.node.off(Node.EventType.LAYER_CHANGED, this.updateLayer, this);
    this.node.off(Node.EventType.ANCHOR_CHANGED, this.updatePos, this);
  }

  private getHorizontalAlign(text: string) {
    if (text === 'left') {
      return HorizontalTextAlignment.LEFT;
    }
    if (text === 'right') {
      return HorizontalTextAlignment.RIGHT;
    }
    if (text === 'center') {
      return HorizontalTextAlignment.CENTER;
    }
    return this.horizontalAlign;
  }

  private getVerticalAlign(text: string) {
    if (text === 'top') {
      return VerticalTextAlignment.TOP;
    }
    if (text === 'bottom') {
      return VerticalTextAlignment.BOTTOM;
    }
    if (text === 'center') {
      return VerticalTextAlignment.CENTER;
    }
    return this.verticalAlign;
  }

  private getNumber(str: string, def: number) {
    if (!str) return def;
    const num = Number(str);
    if (isNaN(num)) return def;
    return num;
  }

  private createLabel(text: string, attrs: IAttrs) {
    const label = RichLabelPool.inst.getLabel();
    label.node.layer = this.node.layer;
    label.string = text;
    label.enableWrapText = false;
    label.useSystemFont = this.useSystemFont;
    label.fontFamily = attrs['family'] || this.fontFamily;
    label.font = this.useSystemFont ? null : this.fonts[this.getNumber(attrs['font'], 0)] || this.fonts[0] || null;
    label.fontSize = this.getNumber(attrs['size'], this.fontSize);
    label.color = attrs['color'] ? new Color(attrs['color']) : this.color;
    label.lineHeight = this.getNumber(attrs['line-height'], this.lineHeight);
    label.isBold = Object.prototype.hasOwnProperty.call(attrs, 'bold');
    label.isItalic = Object.prototype.hasOwnProperty.call(attrs, 'italic');
    label.isUnderline = Object.prototype.hasOwnProperty.call(attrs, 'underline');
    label.cacheMode = this.cacheMode;
    // 监听事件
    if (Object.prototype.hasOwnProperty.call(attrs, 'click')) {
      label.node.on(
        Node.EventType.TOUCH_END,
        function (this: RichTextRedefine) {
          this.node.emit(RichTextRedefine.EventType.CLICK, attrs.click);
        },
        this
      );
    }
    // 阴影
    if (typeof label['enableShadow'] === 'boolean') {
      if (attrs['shadow-blur'] || attrs['shadow-color'] || attrs['shadow-offset']) {
        label['enableShadow'] = true;
        label['shadowBlur'] = this.getNumber(attrs['shadow-blur'], 0);
        label['shadowColor'] = attrs['shadow-color'] ? label['shadowColor'].fromHEX(attrs['shadow-color']) : label['shadowColor'].set(Color.BLACK);
        if (attrs['shadow-offset']) {
          const offset = attrs['shadow-offset'].split(',').map((str) => this.getNumber(str, 0));
          label['shadowOffset'] = label['shadowOffset'].set(offset[0], offset.length >= 2 ? offset[1] : 0);
        } else {
          label['shadowOffset'] = label['shadowOffset'].set(Vec2.ZERO);
        }
      } else {
        label['enableShadow'] = false;
      }
    } else {
      if (attrs['shadow-blur'] || attrs['shadow-color'] || attrs['shadow-offset']) {
        const shadow = label.node.addComponent('cc.LabelShadow') as LabelShadow;
        shadow.blur = this.getNumber(attrs['shadow-blur'], 0);
        shadow.color = attrs['shadow-color'] ? shadow.color.fromHEX(attrs['shadow-color']) : shadow.color.set(Color.BLACK);
        if (attrs['shadow-offset']) {
          const offset = attrs['shadow-offset'].split(',').map((str) => this.getNumber(str, 0));
          shadow.offset = shadow.offset.set(offset[0], offset.length >= 2 ? offset[1] : 0);
        } else {
          shadow.offset = shadow.offset.set(Vec2.ZERO);
        }
      } else {
        label.node.getComponent('cc.LabelShadow')?.destroy();
      }
    }
    // 描边
    if (typeof label['enableOutline'] === 'boolean') {
      if (attrs['outline-color'] || attrs['outline-width']) {
        label['enableOutline'] = true;

        label['outlineWidth'] = this.getNumber(attrs['outline-width'], 0);
        label['outlineColor'] = attrs['outline-color'] ? label['outlineColor'].fromHEX(attrs['outline-color']) : label['outlineColor'].set(Color.BLACK);
      } else {
        label['enableOutline'] = false;
      }
    } else {
      if (attrs['outline-color'] || attrs['outline-width']) {
        const outline = label.node.addComponent('cc.LabelOutline') as LabelOutline;
        outline.width = this.getNumber(attrs['outline-width'], 0);
        outline.color = attrs['outline-color'] ? outline.color.fromHEX(attrs['outline-color']) : outline.color.set(Color.BLACK);
      } else {
        label.node.getComponent('cc.LabelOutline')?.destroy();
      }
    }

    label.updateRenderData(true);
    return label;
  }

  private createLines(tree: XMLParser.IXMLTree, lines?: Line[]) {
    if (!lines) {
      lines = [{ width: 0, height: 0, lineHeight: 0, nodes: [] }];
    }
    tree.children.forEach((tree) => {
      const attrs = tree.attrs;
      const node = tree.node;
      // 文本
      if (node.tag === '#text' && node.text) {
        node.text.split('\n').forEach((text, index) => {
          // 换行
          if (index > 0 && lines[lines.length - 1].nodes.length !== 0) {
            lines.push({ width: 0, height: 0, lineHeight: 0, nodes: [] });
          }

          let count = 0;
          const maxCount = text.length;
          while (text && count++ < maxCount) {
            const lastLine = lines[lines.length - 1];
            const label = this.createLabel(text, attrs);
            const labelUit = label.node.getComponent(UITransform);

            // 超出宽度
            if (this.maxWidth > 0 && lastLine.width + labelUit.width > this.maxWidth) {
              let length = 0;
              if (!this.wholeness) {
                length = defaultLabelSplit(label, labelUit, this.maxWidth - lastLine.width, text);
                // 如果一行连一个字符都容不下，则强制显示一个字符
                if (length <= 0 && lastLine.width === 0) length = 1;
                if (length > 0) {
                  text = text.slice(length);
                  lastLine.width += labelUit.width;
                  lastLine.height = Math.max(lastLine.height, labelUit.height);
                  lastLine.lineHeight = Math.max(lastLine.lineHeight, label.lineHeight);
                  lastLine.nodes.push({ node: label.node, attrs });
                }
                lines.push({ width: 0, height: 0, lineHeight: 0, nodes: [] });
              } else {
                const wholeness = getFirstWholeness(text);
                // 空格
                if (wholeness.type === WholenessType.Space) {
                  label.string = wholeness.text;
                  label.updateRenderData(true);
                  length = wholeness.text.length;
                }
                // 汉字
                else if (wholeness.type === WholenessType.Chinese) {
                  length = defaultLabelSplit(label, labelUit, this.maxWidth - lastLine.width, wholeness.text);
                  // 如果一行连一个字符都容不下，则强制显示一个字符
                  if (length <= 0 && lastLine.width === 0) length = 1;
                }
                // 非汉字
                else {
                  label.string = wholeness.text;
                  label.updateRenderData(true);
                  if (lastLine.width + labelUit.width <= this.maxWidth) {
                    length = wholeness.text.length;
                  } else if (lastLine.width === 0) {
                    length = defaultLabelSplit(label, labelUit, this.maxWidth - lastLine.width, wholeness.text);
                    // 如果一行连一个字符都容不下，则强制显示一个字符
                    if (length <= 0 && lastLine.width === 0) length = 1;
                  }
                }
                if (length > 0) {
                  text = text.slice(length);

                  lastLine.width += labelUit.width;
                  lastLine.height = Math.max(lastLine.height, labelUit.height);
                  lastLine.lineHeight = Math.max(lastLine.lineHeight, label.lineHeight);
                  lastLine.nodes.push({ node: label.node, attrs });
                } else {
                  RichLabelPool.inst.putLabel(label);
                }
                if (length === 0 || lastLine.width >= this.maxWidth - 1) {
                  lines.push({ width: 0, height: 0, lineHeight: 0, nodes: [] });
                }
              }
            }
            // 未超出宽度
            else {
              // wholeness模式下丢弃开头的空格
              if (this.wholeness && lastLine.width === 0) {
                text = StringUtil.TrimStart(text);
                if (text === '') {
                  RichLabelPool.inst.putLabel(label);
                  continue;
                }
                if (label.string !== text) {
                  label.string = text;
                  label.updateRenderData(true);
                }
              }

              text = '';
              lastLine.height = Math.max(lastLine.height, labelUit.height);
              lastLine.lineHeight = Math.max(lastLine.lineHeight, label.lineHeight);
              lastLine.width += labelUit.width;
              lastLine.nodes.push({ node: label.node, attrs });
            }
          }
        });
      }
      // 文本
      else if (node.tag === 'label') {
        this.createLines(tree, lines);
      }
      // 换行
      else if (node.tag === 'br') {
        lines.push({ width: 0, height: 0, lineHeight: 0, nodes: [] });
      }
      // 整行
      else if (node.tag === 'line') {
        const lineHeight = this.getNumber(attrs['line-height'], this.lineHeight);
        lines.push({ width: 0, height: 0, lineHeight: lineHeight - this.lineHeight, nodes: [] });
        lines.push({ width: 0, height: 0, lineHeight: 0, nodes: [] });
      }
      // 图片或节点
      else if (node.tag === 'image' || node.tag === 'node') {
        const lastLine = lines[lines.length - 1];

        const lineHeight = this.getNumber(attrs['line-height'], this.lineHeight);
        const width = this.getNumber(attrs.width, lineHeight);
        const height = this.getNumber(attrs.height, lineHeight);

        const richChild = createNode(node.tag === 'image' ? RichTextImageName : RichTextNodeName);
        richChild.addComponent(UITransform).setContentSize(width, height);

        // 监听事件
        if (Object.prototype.hasOwnProperty.call(tree.attrs, 'click')) {
          richChild.on(
            Node.EventType.TOUCH_END,
            function (this: RichTextRedefine) {
              this.node.emit(RichTextRedefine.EventType.CLICK, tree.attrs.click);
            },
            this
          );
        }

        // 加载资源
        if (node.tag === 'image') {
          if (Object.prototype.hasOwnProperty.call(attrs, 'frame')) {
            const sprite = richChild.addComponent(Sprite);
            sprite.sizeMode = Sprite.SizeMode.CUSTOM;
            sprite.spriteFrame = this.frames[this.getNumber(attrs['frame'], 0)] || null;
          }
        } else {
          if (Object.prototype.hasOwnProperty.call(attrs, 'prefab')) {
            const data = this.prefabs[this.getNumber(attrs['prefab'], 0)];
            if (data) instantiate(data).parent = richChild;
          }
        }

        if (this.maxWidth > 0 && lastLine.width + width > this.maxWidth) {
          lines.push({ width, height: height, lineHeight: lineHeight, nodes: [{ node: richChild, attrs }] });
        } else {
          lastLine.width += width;
          lastLine.height = Math.max(lastLine.height, height);
          lastLine.lineHeight = Math.max(lastLine.lineHeight, lineHeight);
          lastLine.nodes.push({ node: richChild, attrs });
        }
      }
    });
    return lines;
  }

  private updateLines(lines: Line[] = [{ width: 0, height: 0, lineHeight: 0, nodes: [] }]) {
    // 计算容器宽高
    let width = 0;
    let height = 0;
    for (let index = lines.length - 1; index >= 0; index--) {
      const line = lines[index];
      height += index === lines.length - 1 ? line.height || this.lineHeight : line.lineHeight || this.lineHeight;
      width = Math.max(width, line.width);
    }
    if (this.maxWidth > 0) {
      width = this.maxWidth;
    }

    // 设置容器宽高
    const uit = this.node.getComponent(UITransform);
    uit.setContentSize(width, height);

    // 左对齐的起始X坐标
    const leftStartX = -uit.anchorX * width;

    // 起始Y坐标
    let startY = (1 - uit.anchorY) * height;
    let childIndex = 0;
    for (let index = 0; index < lines.length; index++) {
      const line = lines[index];

      // 起始X坐标
      let startX = this.horizontalAlign === HorizontalTextAlignment.LEFT ? leftStartX : this.horizontalAlign === HorizontalTextAlignment.CENTER ? leftStartX + (width - line.width) / 2 : leftStartX + (width - line.width);
      for (let i = 0; i < line.nodes.length; i++, childIndex++) {
        const { node, attrs } = line.nodes[i];
        node.layer = this.node.layer;
        this.node.insertChild(node, childIndex);
        const offsetY = this.getNumber(attrs['offset-y'], 0);
        const verticalAlign = this.getVerticalAlign(attrs['vertical-align']);

        const uit = node.getComponent(UITransform);
        const x = startX + uit.anchorX * uit.width;
        let y = startY + offsetY;

        if (verticalAlign === VerticalTextAlignment.TOP) {
          y -= uit.anchorY * uit.height;
        } else if (verticalAlign === VerticalTextAlignment.CENTER) {
          y -= line.height / 2 + uit.height * (uit.anchorY - 0.5);
        } else if (verticalAlign === VerticalTextAlignment.BOTTOM) {
          y -= uit.anchorY * uit.height + (line.height - uit.height);
        }

        node.setPosition(x, y);
        startX += uit.width;
      }
      startY -= line.lineHeight || this.lineHeight;
    }
  }

  private updateLayer() {
    const children = this.node.children;
    for (let index = children.length - 1; index >= 0; index--) {
      const node = children[index];
      if (node.name === RichTextLabelName || node.name === RichTextImageName || node.name === RichTextNodeName) {
        node.layer = this.node.layer;
      }
    }
  }

  private updatePos() {
    this.updateLines(this.lines);
  }

  private updateRender() {
    const children = this.node.children;
    for (let index = children.length - 1; index >= 0; index--) {
      const node = children[index];
      if (node.name === RichTextLabelName) {
        RichLabelPool.inst.putLabel(node.getComponent(Label));
      } else if (node.name === RichTextImageName || node.name === RichTextNodeName) {
        node.destroy();
      }
    }

    if (!this.string) {
      this.lines = null;
      return;
    }

    const root = XMLParser.parseXML(this.string);
    this.lines = this.createLines(root);

    this.updateLines(this.lines);
  }

  public append(str: string) {
    if (!str) return;
    this._string += str;

    const root = XMLParser.parseXML(str);
    this.lines = this.createLines(root, this.lines);

    const length = this.node.children.length;
    this.updateLines(this.lines);
    return this.node.children.length - length;
  }
}
